﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace EasyConveyor
{
	/// <summary>
	/// A class representing the container of an EasyConveyor application. This class is sealed.
	/// </summary>
	public sealed class Application
	{
		/// <summary>
		/// Raised when an exception was thrown by a middleware.
		/// </summary>
		public event EventHandler<MiddlewareErrorEventArgs> Error;
		/// <summary>
		/// Raised after activation but before any middleware is invoked.
		/// </summary>
		public event EventHandler<RequestReceivedEventArgs> RequestReceived;
		/// <summary>
		/// Raised when the last middleware was run or any other middleware terminates execution. <see cref="IMiddleware.Run" />
		/// </summary>
		public event EventHandler<RequestCompletedEventArgs> RequestCompleted;
		/// <summary>
		/// Raised after a middleware was instantiated. Can be used by the host to configure the middleware instance.
		/// </summary>
		public event EventHandler<MiddlewareInitializeEventArgs> MiddlewareInitialize;

		private readonly IEnumerable<Pipeline> _pipelines;

		/// <summary>
		/// Constructor taking collection of <see cref="PipelineBuilder"/> used to build pipelines with.
		/// </summary>
		/// <param name="pipelineBuilders">An <see cref="IEnumerable{PipelineBuilder}"/> used to build the pipelines with.</param>
		/// <exception cref="ArgumentNullException">Thrown when the value passed for <paramref name="pipelineBuilders"/> is null.</exception>
		/// <exception cref="Exception">Thrown when the value passed for <paramref name="pipelineBuilders"/> contains no <see cref="PipelineBuilder"/> instances.</exception>
		public Application(IEnumerable<PipelineBuilder> pipelineBuilders)
		{
			if (pipelineBuilders == null)
				throw new ArgumentNullException("pipelineBuilders");

			_pipelines = pipelineBuilders.Where(p => p != null).SelectMany(p => p.Build());

			if (!_pipelines.Any())
				throw new Exception("The provided pipeline builder(s) did not yield any pipelines.");

			HookupEvents();
		}

		/// <summary>
		/// Constructor taking collection of <see cref="PipelineBuilder"/> used to build pipelines with.
		/// </summary>
		/// <param name="pipelineBuilder">A <see cref="PipelineBuilder"/> used to build a pipeline with.</param>
		public Application(PipelineBuilder pipelineBuilder)
			: this(new Collection<PipelineBuilder> { pipelineBuilder })
		{
		}

		/// <summary>
		/// Constructor taking collection of <see cref="PipelineBuilder"/> used to build pipelines with.
		/// </summary>
		/// <param name="pipelineBuilders">An array of <see cref="PipelineBuilder"/> used to build the pipelines with.</param>
		public Application(params PipelineBuilder[] pipelineBuilders)
			: this(pipelineBuilders.ToList())
		{
		}

		private void HookupEvents()
		{
			foreach (var pipeline in _pipelines)
			{
				pipeline.Error += PipelineOnError;
				pipeline.RequestCompleted += PipelineOnRequestCompleted;
				pipeline.RequestReceived += PipelineOnRequestReceived;
				pipeline.MiddlewareInitialize += PipelineOnMiddlewareInitialize;
			}
		}

		private void PipelineOnRequestReceived(object sender, RequestReceivedEventArgs requestReceivedEventArgs)
		{
			if (RequestReceived == null) return;

			RequestReceived(this, requestReceivedEventArgs);
		}

		private void PipelineOnRequestCompleted(object sender, RequestCompletedEventArgs requestCompletedEventArgs)
		{
			if (RequestCompleted == null) return;

			RequestCompleted(this, requestCompletedEventArgs);
		}

		private void PipelineOnError(object sender, MiddlewareErrorEventArgs middlewareErrorEventArgs)
		{
			if (Error == null) return;

			Error(this, middlewareErrorEventArgs);
		}

		private void PipelineOnMiddlewareInitialize(object sender, MiddlewareInitializeEventArgs middlewareInitializeEventArgs)
		{
			if (MiddlewareInitialize == null) return;

			MiddlewareInitialize(this, middlewareInitializeEventArgs);
		}

		/// <summary>
		/// Sets an <see cref="Action{T}"/> of type <see cref="string"/> representing the delegate to call when messages are logged. May be null.
		/// </summary>
		public Action<string> Logger
		{
			set
			{
				foreach (var pipeline in _pipelines)
				{
					pipeline.Logger = value;
				}
			}
		}
	}
}
